//
// spu.c
//
// CSE 420
// Project 4 : Part 3b
// Authors: Colin Hofman, Alan Tu, Adam Fairfield
//

// System includes
#include <stdio.h>
#include <string.h>
#include <spu_mfcio.h>

// Local includes
#include "common.h"

// Global variables
int blockA_InSPE[BLOCK_SIZE_SQUARED] QWORD_ALIGNED;   // Matrix block A
int blockB_InSPE[BLOCK_SIZE_SQUARED] QWORD_ALIGNED;   // Matrix block B
int blockC_InSPE[BLOCK_SIZE_SQUARED] QWORD_ALIGNED;   // Result matrix block C
CONTROL_BLOCK cb QWORD_ALIGNED;                       // Control block holding data from ppu

// Constant size of a data element in blocks
const int dt_unit = 4096;

// Forward function declarations
void readControlBlock(uint64_t *argp);
void readMatrixBlock(int *spuptr, int *ppuptr);

//
// Main spu thread.
//
// @param   uint64_t speid    SPE id
//          uint64_t argp     Argument pointer
//          uint64_t envp     Environment pointer
// @return  int               Return status
//
int main(int speid, uint64_t argp, uint64_t envp)
{
	// Read control block
	readControlBlock(&argp);

   // Load matrix blocks from ppu to spu
	readMatrixBlock(&blockA_InSPE[0], cb.blockA);
	readMatrixBlock(&blockB_InSPE[0], cb.blockB);
	readMatrixBlock(&blockC_InSPE[0], cb.SPU_result);

	// Multiply just the blocks
   for (int i = 0; i < BLOCK_SIZE; ++i)
      for (int j = 0; j < BLOCK_SIZE; ++j)
         for (int k = 0; k < BLOCK_SIZE; ++k)
            blockC_InSPE[i * BLOCK_SIZE + j] += (blockA_InSPE[i * BLOCK_SIZE + k] * blockB_InSPE[k * BLOCK_SIZE + j]);

	// Write output data
	mfc_put(&blockC_InSPE, cb.SPU_result, sizeof(int) * BLOCK_SIZE_SQUARED, 2, 0, 0);
	mfc_write_tag_mask(1 << 2);
	mfc_read_tag_status_all();
}

//
// Read control block data from PPE
//
// @param   uint64_t*   argp  Argument pointer
//
void readControlBlock(uint64_t *argp)
{
	mfc_get(&cb, *argp, sizeof(cb), 0, 0, 0);
	mfc_write_tag_mask(1 << 0);
	mfc_read_tag_status_all();
}

//
// Read a given matrix block from the PPE
//
// @param   int*  spuptr   Where the data will be stored on the SPU
//          int*  ppuptr   Where the data is stored on the PPU
//
void readMatrixBlock(int *spuptr, int *ppuptr)
{
   // Compute end of data block
	int *spulast = spuptr + BLOCK_SIZE_SQUARED;
   
   // Read data from ppu->spu
	while (spuptr < spulast)
	{
      // Read a block from ppu
		int nwords = ( spuptr + dt_unit > spulast ) ? (spulast - spuptr) : dt_unit;
		mfc_get(spuptr, ppuptr, 4 * nwords, 0, 0, 0);
		mfc_write_tag_mask(1 << 0);
		mfc_read_tag_status_all();

      // Advance both pointers for next block of data
		spuptr += dt_unit;
		ppuptr += dt_unit;
	}
}